/*-------------------------------------------------------------------------
 *
 * heaptuple.c
 *	  本文件包含堆元组的访问器和变更例程，以及各种元组实用程序。
 *
 * 关于varlenas和此代码的一些说明：
 *
 * 在Postgres 8.3之前，varlenas总是具有4字节长度头，因此总是需要4字节对齐（至少）。这为短的varlenas浪费了空间，例如CHAR(1)占用5字节，并且可能需要最多3个额外的填充字节以实现对齐。
 *
 * 现在，短varlena（最多126个数据字节）被缩减为1字节头，并且我们不再进行对齐。为了将这一点隐藏起来，以便数据类型特定的函数不必处理它，这种数据被认为是“烤过的”，并将通过pg_detoast_datum扩展回正常的4字节头格式。
 * （在性能关键的代码路径中，我们可以使用pg_detoast_datum_packed和适当的访问宏来避免这种开销。）请注意，此转换是在heap_form_tuple中直接执行的，而不调用heaptoast.c。
 *
 * 这一变化将破坏任何假设在元组中放置后值不需要被解烤的代码。希望这种情况很少。
 *
 * Varlenas在pg_type/pg_attribute中仍然具有对齐INT（或DOUBLE），因为这是未烤格式的正常要求。但我们忽略了1字节头格式的要求。这意味着varlena数据的实际起始位置可能会根据其格式而变化。为了确定存储内容，我们必须要求对齐填充字节为零。
 * （Postgres实际上一直将它们归零，但现在这是必需的！）由于1字节头varlena的第一个字节永远不能为零，我们可以检查前一个数据之后的第一个字节，以判断它是填充字节还是1字节头varlena的开始。
 *
 * 请注意，虽然以前我们可以依赖系统目录的第一个varlena列位于C结构为目录建议的偏移量，但现在这是有风险的：只有在前一个字段是字对齐的情况下才安全，这样就不会有任何填充。
 *
 * 我们不打包attstorage为PLAIN的varlenas，因为数据类型不期待需要解烤值。这在oidvector和int2vector中特别使用，这些在系统目录中使用，并且我们希望通过C结构偏移量仍然引用它们。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/access/common/heaptuple.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/heaptoast.h"
#include "access/sysattr.h"
#include "access/tupdesc_details.h"
#include "common/hashfn.h"
#include "executor/tuptable.h"
#include "utils/datum.h"
#include "utils/expandeddatum.h"
#include "utils/hsearch.h"
#include "utils/memutils.h"


/*
 * att的数据类型是否允许打包成1字节头的varlena格式？
 * 虽然使用TupleDescAttr()并将attstorage = 
 * TYPSTORAGE_PLAIN的函数无法使用打包的varlena头，但调用
 * TupleDescInitEntry()的函数使用typeForm->typstorage (TYPSTORAGE_EXTENDED) 
 * 并且可以使用打包的varlena头，例如：
 *     CREATE TABLE test(a VARCHAR(10000) STORAGE PLAIN);
 *     INSERT INTO test VALUES (repeat('A',10));
 * 这可以通过pageinspect进行验证。
 */
#define ATT_IS_PACKABLE(att) \
	((att)->attlen == -1 && (att)->attstorage != TYPSTORAGE_PLAIN)
/* 如果已知varlena，则使用此项 */
#define VARLENA_ATT_IS_PACKABLE(att) \
	((att)->attstorage != TYPSTORAGE_PLAIN)

/*
 * 设置以缓存传值引用缺失属性的方式，以便在tupleDesc销毁时保持有效。
 */

typedef struct
{
	int			len;
	Datum		value;
} missing_cache_key;

static HTAB *missing_cache = NULL;

static uint32 fc_missing_hash(const void *fc_key, Size fc_keysize)
{
	const missing_cache_key *fc_entry = (missing_cache_key *) fc_key;

	return hash_bytes((const unsigned char *) fc_entry->value, fc_entry->len);
}

static int fc_missing_match(const void *fc_key1, const void *fc_key2, Size fc_keysize)
{
	const missing_cache_key *fc_entry1 = (missing_cache_key *) fc_key1;
	const missing_cache_key *fc_entry2 = (missing_cache_key *) fc_key2;

	if (fc_entry1->len != fc_entry2->len)
		return fc_entry1->len > fc_entry2->len ? 1 : -1;

	return memcmp(DatumGetPointer(fc_entry1->value),
				  DatumGetPointer(fc_entry2->value),
				  fc_entry1->len);
}

static void fc_init_missing_cache()
{
	HASHCTL		fc_hash_ctl;

	fc_hash_ctl.keysize = sizeof(missing_cache_key);
	fc_hash_ctl.entrysize = sizeof(missing_cache_key);
	fc_hash_ctl.hcxt = TopMemoryContext;
	fc_hash_ctl.hash = fc_missing_hash;
	fc_hash_ctl.match = fc_missing_match;
	missing_cache =
		hash_create("Missing Values Cache",
					32,
					&fc_hash_ctl,
					HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION | HASH_COMPARE);
}

/* ----------------------------------------------------------------
 *						杂项支持例程
 * ----------------------------------------------------------------
 */

/*
 * 返回属性的缺失值，如果没有则返回NULL。
 */
Datum getmissingattr(TupleDesc fc_tupleDesc,
			   int fc_attnum, bool *fc_isnull)
{
	Form_pg_attribute fc_att;

	Assert(fc_attnum <= fc_tupleDesc->natts);
	Assert(fc_attnum > 0);

	fc_att = TupleDescAttr(fc_tupleDesc, fc_attnum - 1);

	if (fc_att->atthasmissing)
	{
		AttrMissing *fc_attrmiss;

		Assert(fc_tupleDesc->constr);
		Assert(fc_tupleDesc->constr->missing);

		fc_attrmiss = fc_tupleDesc->constr->missing + (fc_attnum - 1);

		if (fc_attrmiss->am_present)
		{
			missing_cache_key fc_key;
			missing_cache_key *fc_entry;
			bool		fc_found;
			MemoryContext fc_oldctx;

			*fc_isnull = false;

			/* 不需要缓存传值属性 */
			if (fc_att->attbyval)
				return fc_attrmiss->am_value;

			/* 如果需要，则设置缓存 */
			if (missing_cache == NULL)
				fc_init_missing_cache();

			/* 检查是否有缓存条目 */
			Assert(fc_att->attlen > 0 || fc_att->attlen == -1);
			if (fc_att->attlen > 0)
				fc_key.len = fc_att->attlen;
			else
				fc_key.len = VARSIZE_ANY(fc_attrmiss->am_value);
			fc_key.value = fc_attrmiss->am_value;

			fc_entry = hash_search(missing_cache, &fc_key, HASH_ENTER, &fc_found);

			if (!fc_found)
			{
				/* 缓存未命中，因此我们需要datum的非瞬态副本 */
				fc_oldctx = MemoryContextSwitchTo(TopMemoryContext);
				fc_entry->value =
					datumCopy(fc_attrmiss->am_value, false, fc_att->attlen);
				MemoryContextSwitchTo(fc_oldctx);
			}

			return fc_entry->value;
		}
	}

	*fc_isnull = true;
	return PointerGetDatum(NULL);
}

/*
 * heap_compute_data_size
 *		确定要构造的元组的数据区域大小
 */
Size heap_compute_data_size(TupleDesc fc_tupleDesc,
					   Datum *fc_values,
					   bool *fc_isnull)
{
	Size		fc_data_length = 0;
	int			fc_i;
	int			fc_numberOfAttributes = fc_tupleDesc->natts;

	for (fc_i = 0; fc_i < fc_numberOfAttributes; fc_i++)
	{
		Datum		fc_val;
		Form_pg_attribute fc_atti;

		if (fc_isnull[fc_i])
			continue;

		fc_val = fc_values[fc_i];
		fc_atti = TupleDescAttr(fc_tupleDesc, fc_i);

		if (ATT_IS_PACKABLE(fc_atti) &&
			VARATT_CAN_MAKE_SHORT(DatumGetPointer(fc_val)))
		{
			/*
			 * 我们预计转换为短varlena头，因此
			 * 调整长度，并且不计算任何对齐
			 */
			fc_data_length += VARATT_CONVERTED_SHORT_SIZE(DatumGetPointer(fc_val));
		}
		else if (fc_atti->attlen == -1 &&
				 VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(fc_val)))
		{
			/*
			 * 我们希望扁平化扩展值，以便构造的
			 * 元组不依赖于它
			 */
			fc_data_length = att_align_nominal(fc_data_length, fc_atti->attalign);
			fc_data_length += EOH_get_flat_size(DatumGetEOHP(fc_val));
		}
		else
		{
			fc_data_length = att_align_datum(fc_data_length, fc_atti->attalign,
										  fc_atti->attlen, fc_val);
			fc_data_length = att_addlength_datum(fc_data_length, fc_atti->attlen,
											  fc_val);
		}
	}

	return fc_data_length;
}

/*
 * 每个属性的heap_fill_tuple和其他构造元组的例程的帮助函数。
 *
 * 填充数据值或null位掩码中的位
 */
static inline void fc_fill_val(Form_pg_attribute fc_att,
		 bits8 **fc_bit,
		 int *fc_bitmask,
		 char **fc_dataP,
		 uint16 *fc_infomask,
		 Datum fc_datum,
		 bool fc_isnull)
{
	Size		fc_data_length;
	char	   *fc_data = *fc_dataP;

	/*
	 * 如果我们正在构建null位图，这里为
	 * 当前列值设置相应的位。
	 */
	if (fc_bit != NULL)
	{
		if (*fc_bitmask != HIGHBIT)
			*fc_bitmask <<= 1;
		else
		{
			*fc_bit += 1;
			**fc_bit = 0x0;
			*fc_bitmask = 1;
		}

		if (fc_isnull)
		{
			*fc_infomask |= HEAP_HASNULL;
			return;
		}

		**fc_bit |= *fc_bitmask;
	}

	/*
	 * XXX 我们在指针值本身上使用att_align宏，而不是
	 * 在偏移量上。这有点像hack。
	 */
	if (fc_att->attbyval)
	{
		/* 传值引用 */
		fc_data = (char *) att_align_nominal(fc_data, fc_att->attalign);
		store_att_byval(fc_data, fc_datum, fc_att->attlen);
		fc_data_length = fc_att->attlen;
	}
	else if (fc_att->attlen == -1)
	{
		/* varlena */
		Pointer		fc_val = DatumGetPointer(fc_datum);

		*fc_infomask |= HEAP_HASVARWIDTH;
		if (VARATT_IS_EXTERNAL(fc_val))
		{
			if (VARATT_IS_EXTERNAL_EXPANDED(fc_val))
			{
				/*
				 * 我们希望扁平化扩展值，以便
				 * 构造的元组不依赖于它
				 */
				ExpandedObjectHeader *fc_eoh = DatumGetEOHP(fc_datum);

				fc_data = (char *) att_align_nominal(fc_data,
												  fc_att->attalign);
				fc_data_length = EOH_get_flat_size(fc_eoh);
				EOH_flatten_into(fc_eoh, fc_data, fc_data_length);
			}
			else
			{
				*fc_infomask |= HEAP_HASEXTERNAL;
				/* 不对齐，因为根据定义它是短的 */
				fc_data_length = VARSIZE_EXTERNAL(fc_val);
				memcpy(fc_data, fc_val, fc_data_length);
			}
		}
		else if (VARATT_IS_SHORT(fc_val))
		{
			/* 短varlenas不需要对齐 */
			fc_data_length = VARSIZE_SHORT(fc_val);
			memcpy(fc_data, fc_val, fc_data_length);
		}
		else if (VARLENA_ATT_IS_PACKABLE(fc_att) &&
				 VARATT_CAN_MAKE_SHORT(fc_val))
		{
			/* 转换为短varlena -- 不需要对齐 */
			fc_data_length = VARATT_CONVERTED_SHORT_SIZE(fc_val);
			SET_VARSIZE_SHORT(fc_data, fc_data_length);
			memcpy(fc_data + 1, VARDATA(fc_val), fc_data_length - 1);
		}
		else
		{
			/* 完整的4字节头varlena */
			fc_data = (char *) att_align_nominal(fc_data,
											  fc_att->attalign);
			fc_data_length = VARSIZE(fc_val);
			memcpy(fc_data, fc_val, fc_data_length);
		}
	}
	else if (fc_att->attlen == -2)
	{
		/* cstring ... 永远不需要对齐 */
		*fc_infomask |= HEAP_HASVARWIDTH;
		Assert(fc_att->attalign == TYPALIGN_CHAR);
		fc_data_length = strlen(DatumGetCString(fc_datum)) + 1;
		memcpy(fc_data, DatumGetPointer(fc_datum), fc_data_length);
	}
	else
	{
		/* 固定长度的传值引用 */
		fc_data = (char *) att_align_nominal(fc_data, fc_att->attalign);
		Assert(fc_att->attlen > 0);
		fc_data_length = fc_att->attlen;
		memcpy(fc_data, DatumGetPointer(fc_datum), fc_data_length);
	}

	fc_data += fc_data_length;
	*fc_dataP = fc_data;
}

/*
 * heap_fill_tuple
 *		从values/isnull数组加载元组的数据部分
 *
 * 我们还填充null位图（如果有）并设置反映元组
 * 数据内容的infomask位。
 *
 * 注意：现在要求调用方预先将数据区域设置为零。
 */
void heap_fill_tuple(TupleDesc fc_tupleDesc,
				Datum *fc_values, bool *fc_isnull,
				char *fc_data, Size fc_data_size,
				uint16 *fc_infomask, bits8 *fc_bit)
{
	bits8	   *fc_bitP;
	int			fc_bitmask;
	int			fc_i;
	int			fc_numberOfAttributes = fc_tupleDesc->natts;

#ifdef USE_ASSERT_CHECKING
	char	   *fc_start = fc_data;
#endif

	if (fc_bit != NULL)
	{
		fc_bitP = &fc_bit[-1];
		fc_bitmask = HIGHBIT;
	}
	else
	{
		/* 只是为了让编译器安静 */
		fc_bitP = NULL;
		fc_bitmask = 0;
	}

	*fc_infomask &= ~(HEAP_HASNULL | HEAP_HASVARWIDTH | HEAP_HASEXTERNAL);

	for (fc_i = 0; fc_i < fc_numberOfAttributes; fc_i++)
	{
		Form_pg_attribute fc_attr = TupleDescAttr(fc_tupleDesc, fc_i);

		fc_fill_val(fc_attr,
				 fc_bitP ? &fc_bitP : NULL,
				 &fc_bitmask,
				 &fc_data,
				 fc_infomask,
				 fc_values ? fc_values[fc_i] : PointerGetDatum(NULL),
				 fc_isnull ? fc_isnull[fc_i] : true);
	}

	Assert((fc_data - fc_start) == fc_data_size);
}


/* ----------------------------------------------------------------
 *						heap tuple接口
 * ----------------------------------------------------------------
 */

/* ----------------
 *		heap_attisnull	- 如果元组属性不存在，则返回true
 * ----------------
 */
bool heap_attisnull(HeapTuple fc_tup, int fc_attnum, TupleDesc fc_tupleDesc)
{
	/*
	 * 我们允许一个 NULL tupledesc 对于不预期有缺失值的关系，如目录关系和索引。
	 */
	Assert(!fc_tupleDesc || fc_attnum <= fc_tupleDesc->natts);
	if (fc_attnum > (int) HeapTupleHeaderGetNatts(fc_tup->t_data))
	{
		if (fc_tupleDesc && TupleDescAttr(fc_tupleDesc, fc_attnum - 1)->atthasmissing)
			return false;
		else
			return true;
	}

	if (fc_attnum > 0)
	{
		if (HeapTupleNoNulls(fc_tup))
			return false;
		return att_isnull(fc_attnum - 1, fc_tup->t_data->t_bits);
	}

	switch (fc_attnum)
	{
		case TableOidAttributeNumber:
		case SelfItemPointerAttributeNumber:
		case MinTransactionIdAttributeNumber:
		case MinCommandIdAttributeNumber:
		case MaxTransactionIdAttributeNumber:
		case MaxCommandIdAttributeNumber:
			/* 这些永远不会是 null */
			break;

		default:
			elog(ERROR, "invalid attnum: %d", fc_attnum);
	}

	return false;
}

/* ----------------
 *		nocachegetattr
 *
 *		这个只在 fastgetattr() 宏中被调用，在我们不能使用 cacheoffset 且值不是 null 的情况下。
 *
 *		这在属性描述符中缓存属性偏移量。
 *
 *		加速的另一种方法是将偏移量与元组一起缓存，但这似乎更困难，除非你接受实际将这些偏移量放入你发送到磁盘的元组中的存储损失。讨厌。
 *
 *		这个方案的速度会比那种稍慢，但对于命中大量元组的查询应该表现良好。在你缓存偏移量一次之后，使用相同属性描述符检查所有其他元组会快得多。 -cim 5/4/91
 *
 *		注意：如果你需要更改这个代码，请参见 heap_deform_tuple。
 *		还请参见 nocache_index_getattr，这是索引元组的相同代码。
 * ----------------
 */
Datum nocachegetattr(HeapTuple fc_tuple,
			   int fc_attnum,
			   TupleDesc fc_tupleDesc)
{
	HeapTupleHeader fc_tup = fc_tuple->t_data;
	char	   *fc_tp;				/* 指向元组数据部分的指针 */
	bits8	   *fc_bp = fc_tup->t_bits;	/* 指向元组中 null 位图的指针 */
	bool		fc_slow = false;	/* 我们是否需要遍历属性？ */
	int			fc_off;			/* 数据中的当前偏移量 */

	/* ----------------
	 *	 三种情况：
	 *
	 *	 1: 没有 null 和没有可变宽度属性。
	 *	 2: 在属性之后有一个 null 或可变宽度。
	 *	 3: 在属性之前有 null 或可变宽度。
	 * ----------------
	 */

	fc_attnum--;

	if (!HeapTupleNoNulls(fc_tuple))
	{
		/*
		 * 元组中某个地方有一个 null
		 *
		 * 检查是否有任何前面的位是 null...
		 */
		int			fc_byte = fc_attnum >> 3;
		int			fc_finalbit = fc_attnum & 0x07;

		/* 检查最后一个字节的最终位“之前”的 null */
		if ((~fc_bp[fc_byte]) & ((1 << fc_finalbit) - 1))
			fc_slow = true;
		else
		{
			/* 检查任何“较早”字节中的 null */
			int			fc_i;

			for (fc_i = 0; fc_i < fc_byte; fc_i++)
			{
				if (fc_bp[fc_i] != 0xFF)
				{
					fc_slow = true;
					break;
				}
			}
		}
	}

	fc_tp = (char *) fc_tup + fc_tup->t_hoff;

	if (!fc_slow)
	{
		Form_pg_attribute fc_att;

		/*
		 * 如果我们到这里，目标属性之前（包括目标属性）没有 null。
		 * 如果我们有一个缓存偏移量，我们可以使用它。
		 */
		fc_att = TupleDescAttr(fc_tupleDesc, fc_attnum);
		if (fc_att->attcacheoff >= 0)
			return fetchatt(fc_att, fc_tp + fc_att->attcacheoff);

		/*
		 * 否则，检查目标属性之前（包括目标属性）是否有非固定长度的属性。
		 * 如果没有，它安全地便宜地初始化这些属性的缓存偏移量。
		 */
		if (HeapTupleHasVarWidth(fc_tuple))
		{
			int			fc_j;

			for (fc_j = 0; fc_j <= fc_attnum; fc_j++)
			{
				if (TupleDescAttr(fc_tupleDesc, fc_j)->attlen <= 0)
				{
					fc_slow = true;
					break;
				}
			}
		}
	}

	if (!fc_slow)
	{
		int			fc_natts = fc_tupleDesc->natts;
		int			fc_j = 1;

		/*
		 * 如果我们到这里，我们有一个元组，到目标属性（包括目标属性）没有 null 或可变宽度，
		 * 因此我们可以使用缓存偏移量... 只不过我们还没有它，否则我们不会到这里。
		 * 由于计算固定宽列的偏移量是便宜的，我们抓住机会为*所有*前导固定宽列初始化缓存偏移量，
		 * 希望避免将来访问这个例程。
		 */
		TupleDescAttr(fc_tupleDesc, 0)->attcacheoff = 0;

		/* 我们可能在之前的慢路径中设置了一些偏移量 */
		while (fc_j < fc_natts && TupleDescAttr(fc_tupleDesc, fc_j)->attcacheoff > 0)
			fc_j++;

		fc_off = TupleDescAttr(fc_tupleDesc, fc_j - 1)->attcacheoff +
			TupleDescAttr(fc_tupleDesc, fc_j - 1)->attlen;

		for (; fc_j < fc_natts; fc_j++)
		{
			Form_pg_attribute fc_att = TupleDescAttr(fc_tupleDesc, fc_j);

			if (fc_att->attlen <= 0)
				break;

			fc_off = att_align_nominal(fc_off, fc_att->attalign);

			fc_att->attcacheoff = fc_off;

			fc_off += fc_att->attlen;
		}

		Assert(fc_j > fc_attnum);

		fc_off = TupleDescAttr(fc_tupleDesc, fc_attnum)->attcacheoff;
	}
	else
	{
		bool		fc_usecache = true;
		int			fc_i;

		/*
		 * 现在我们知道必须小心地遍历元组。但是我们仍然
		 * 可能能够缓存一些偏移量以便下次使用。
		 *
		 * 注意 - 这个循环有点棘手。对于每个非空属性，
		 * 我们必须首先计算属性之前的对齐填充，
		 * 然后根据其长度推进属性。空值没有
		 * 存储，也没有对齐填充。我们可以使用/设置
		 * attcacheoff，直到我们遇到空值或可变宽度属性。
		 */
		fc_off = 0;
		for (fc_i = 0;; fc_i++)		/* 循环退出在“break”处 */
		{
			Form_pg_attribute fc_att = TupleDescAttr(fc_tupleDesc, fc_i);

			if (HeapTupleHasNulls(fc_tuple) && att_isnull(fc_i, fc_bp))
			{
				fc_usecache = false;
				continue;		/* 这不能是目标属性 */
			}

			/* 如果我们知道下一个偏移量，我们可以跳过其余部分 */
			if (fc_usecache && fc_att->attcacheoff >= 0)
				fc_off = fc_att->attcacheoff;
			else if (fc_att->attlen == -1)
			{
				/*
				 * 只有当偏移量已经对齐合适时，才能缓存
				 * 变长属性的偏移量，这样在任何情况下都不会有
				 * 填充字节：那么偏移量对
				 * 对齐或未对齐的值都是有效的。
				 */
				if (fc_usecache &&
					fc_off == att_align_nominal(fc_off, fc_att->attalign))
					fc_att->attcacheoff = fc_off;
				else
				{
					fc_off = att_align_pointer(fc_off, fc_att->attalign, -1,
											fc_tp + fc_off);
					fc_usecache = false;
				}
			}
			else
			{
				/* 不是 varlena，因此可以安全使用 att_align_nominal */
				fc_off = att_align_nominal(fc_off, fc_att->attalign);

				if (fc_usecache)
					fc_att->attcacheoff = fc_off;
			}

			if (fc_i == fc_attnum)
				break;

			fc_off = att_addlength_pointer(fc_off, fc_att->attlen, fc_tp + fc_off);

			if (fc_usecache && fc_att->attlen <= 0)
				fc_usecache = false;
		}
	}

	return fetchatt(TupleDescAttr(fc_tupleDesc, fc_attnum), fc_tp + fc_off);
}

/* ----------------
 *		heap_getsysattr
 *
 *		获取元组的系统属性值。
 *
 * 这是 heap_getattr 宏的一个支持例程。该宏
 * 已经确定 attnum 指的是一个系统属性。
 * ----------------
 */
Datum heap_getsysattr(HeapTuple fc_tup, int fc_attnum, TupleDesc fc_tupleDesc, bool *fc_isnull)
{
	Datum		fc_result;

	Assert(fc_tup);

	/* 目前，没有系统属性会被读取为 NULL。 */
	*fc_isnull = false;

	switch (fc_attnum)
	{
		case SelfItemPointerAttributeNumber:
			/* 通过引用传递的数据类型 */
			fc_result = PointerGetDatum(&(fc_tup->t_self));
			break;
		case MinTransactionIdAttributeNumber:
			fc_result = TransactionIdGetDatum(HeapTupleHeaderGetRawXmin(fc_tup->t_data));
			break;
		case MaxTransactionIdAttributeNumber:
			fc_result = TransactionIdGetDatum(HeapTupleHeaderGetRawXmax(fc_tup->t_data));
			break;
		case MinCommandIdAttributeNumber:
		case MaxCommandIdAttributeNumber:

			/*
			 * cmin 和 cmax 现在都是同一个字段的别名，
			 * 它实际上也可以是一个组合命令 ID。XXX 也许我们应该
			 * 如果可能的话返回“真实”的 cmin 或 cmax，即如果我们
			 * 在发起事务内部？
			 */
			fc_result = CommandIdGetDatum(HeapTupleHeaderGetRawCommandId(fc_tup->t_data));
			break;
		case TableOidAttributeNumber:
			fc_result = ObjectIdGetDatum(fc_tup->t_tableOid);
			break;
		default:
			elog(ERROR, "invalid attnum: %d", fc_attnum);
			fc_result = 0;			/* 保持编译器安静 */
			break;
	}
	return fc_result;
}

/* ----------------
 *		heap_copytuple
 *
 *		返回整个元组的副本
 *
 * HeapTuple 结构、元组头和元组数据都作为一个
 * 单独的 palloc() 块分配。
 * ----------------
 */
HeapTuple heap_copytuple(HeapTuple fc_tuple)
{
	HeapTuple	fc_newTuple;

	if (!HeapTupleIsValid(fc_tuple) || fc_tuple->t_data == NULL)
		return NULL;

	fc_newTuple = (HeapTuple) palloc(HEAPTUPLESIZE + fc_tuple->t_len);
	fc_newTuple->t_len = fc_tuple->t_len;
	fc_newTuple->t_self = fc_tuple->t_self;
	fc_newTuple->t_tableOid = fc_tuple->t_tableOid;
	fc_newTuple->t_data = (HeapTupleHeader) ((char *) fc_newTuple + HEAPTUPLESIZE);
	memcpy((char *) fc_newTuple->t_data, (char *) fc_tuple->t_data, fc_tuple->t_len);
	return fc_newTuple;
}

/* ----------------
 *		heap_copytuple_with_tuple
 *
 *		将元组复制到调用者提供的 HeapTuple 管理结构中
 *
 * 注意，调用此函数后，“dest”HeapTuple 将不会
 * 作为一个单独的 palloc() 块分配（与 heap_copytuple() 不同）。
 * ----------------
 */
void heap_copytuple_with_tuple(HeapTuple fc_src, HeapTuple fc_dest)
{
	if (!HeapTupleIsValid(fc_src) || fc_src->t_data == NULL)
	{
		fc_dest->t_data = NULL;
		return;
	}

	fc_dest->t_len = fc_src->t_len;
	fc_dest->t_self = fc_src->t_self;
	fc_dest->t_tableOid = fc_src->t_tableOid;
	fc_dest->t_data = (HeapTupleHeader) palloc(fc_src->t_len);
	memcpy((char *) fc_dest->t_data, (char *) fc_src->t_data, fc_src->t_len);
}

/*
 * 扩展一个属性少于所需的元组。对于每个在
 * sourceTuple 中缺失的属性，如果有缺失的值将被
 * 使用。否则该属性将被设置为 NULL。
 *
 * 源元组的属性必须少于所需数量。
 *
 * 只能提供 targetHeapTuple 和 targetMinimalTuple 之一。另一个
 * 参数必须为 NULL。
 */
static void fc_expand_tuple(HeapTuple *fc_targetHeapTuple,
			 MinimalTuple *fc_targetMinimalTuple,
			 HeapTuple fc_sourceTuple,
			 TupleDesc fc_tupleDesc)
{
	AttrMissing *fc_attrmiss = NULL;
	int			fc_attnum;
	int			fc_firstmissingnum;
	bool		fc_hasNulls = HeapTupleHasNulls(fc_sourceTuple);
	HeapTupleHeader fc_targetTHeader;
	HeapTupleHeader fc_sourceTHeader = fc_sourceTuple->t_data;
	int			fc_sourceNatts = HeapTupleHeaderGetNatts(fc_sourceTHeader);
	int			fc_natts = fc_tupleDesc->natts;
	int			fc_sourceNullLen;
	int			fc_targetNullLen;
	Size		fc_sourceDataLen = fc_sourceTuple->t_len - fc_sourceTHeader->t_hoff;
	Size		fc_targetDataLen;
	Size		fc_len;
	int			fc_hoff;
	bits8	   *fc_nullBits = NULL;
	int			fc_bitMask = 0;
	char	   *fc_targetData;
	uint16	   *fc_infoMask;

	Assert((fc_targetHeapTuple && !fc_targetMinimalTuple)
		   || (!fc_targetHeapTuple && fc_targetMinimalTuple));

	Assert(fc_sourceNatts < fc_natts);

	fc_sourceNullLen = (fc_hasNulls ? BITMAPLEN(fc_sourceNatts) : 0);

	fc_targetDataLen = fc_sourceDataLen;

	if (fc_tupleDesc->constr &&
		fc_tupleDesc->constr->missing)
	{
		/*
		 * 如果有缺失的值，我们希望将它们放入元组中。
		 * 在此之前，我们必须计算值数组和
		 * 可变长度数据的额外长度。
		 */
		fc_attrmiss = fc_tupleDesc->constr->missing;

		/*
		 * 找到 attrmiss 中第一个我们在
		 * 源中没有值的项目。我们可以忽略所有缺失的条目。
		 */
		for (fc_firstmissingnum = fc_sourceNatts;
			 fc_firstmissingnum < fc_natts;
			 fc_firstmissingnum++)
		{
			if (fc_attrmiss[fc_firstmissingnum].am_present)
				break;
			else
				fc_hasNulls = true;
		}

		/*
		 * 现在遍历缺失的属性。如果有缺失值，则为其留出空间。
		 * 否则，它将变为NULL。
		 */
		for (fc_attnum = fc_firstmissingnum;
			 fc_attnum < fc_natts;
			 fc_attnum++)
		{
			if (fc_attrmiss[fc_attnum].am_present)
			{
				Form_pg_attribute fc_att = TupleDescAttr(fc_tupleDesc, fc_attnum);

				fc_targetDataLen = att_align_datum(fc_targetDataLen,
												fc_att->attalign,
												fc_att->attlen,
												fc_attrmiss[fc_attnum].am_value);

				fc_targetDataLen = att_addlength_pointer(fc_targetDataLen,
													  fc_att->attlen,
													  fc_attrmiss[fc_attnum].am_value);
			}
			else
			{
				/* 没有缺失值，因此它必须是null */
				fc_hasNulls = true;
			}
		}
	}							/* 如果有缺失值，结束 */
	else
	{
		/*
		 * 如果根本没有缺失值，则必须允许NULL，因为一些属性被认定为缺失。
		 */
		fc_hasNulls = true;
	}

	fc_len = 0;

	if (fc_hasNulls)
	{
		fc_targetNullLen = BITMAPLEN(fc_natts);
		fc_len += fc_targetNullLen;
	}
	else
		fc_targetNullLen = 0;

	/*
	 * 分配并清零所需的空间。注意，元组主体和
	 * HeapTupleData管理结构是以一块分配的。
	 */
	if (fc_targetHeapTuple)
	{
		fc_len += offsetof(HeapTupleHeaderData, t_bits);
		fc_hoff = fc_len = MAXALIGN(fc_len); /* 安全对齐用户数据 */
		fc_len += fc_targetDataLen;

		*fc_targetHeapTuple = (HeapTuple) palloc0(HEAPTUPLESIZE + fc_len);
		(*fc_targetHeapTuple)->t_data
			= fc_targetTHeader
			= (HeapTupleHeader) ((char *) *fc_targetHeapTuple + HEAPTUPLESIZE);
		(*fc_targetHeapTuple)->t_len = fc_len;
		(*fc_targetHeapTuple)->t_tableOid = fc_sourceTuple->t_tableOid;
		(*fc_targetHeapTuple)->t_self = fc_sourceTuple->t_self;

		fc_targetTHeader->t_infomask = fc_sourceTHeader->t_infomask;
		fc_targetTHeader->t_hoff = fc_hoff;
		HeapTupleHeaderSetNatts(fc_targetTHeader, fc_natts);
		HeapTupleHeaderSetDatumLength(fc_targetTHeader, fc_len);
		HeapTupleHeaderSetTypeId(fc_targetTHeader, fc_tupleDesc->tdtypeid);
		HeapTupleHeaderSetTypMod(fc_targetTHeader, fc_tupleDesc->tdtypmod);
		/* 我们还确保t_ctid无效，除非明确设置 */
		ItemPointerSetInvalid(&(fc_targetTHeader->t_ctid));
		if (fc_targetNullLen > 0)
			fc_nullBits = (bits8 *) ((char *) (*fc_targetHeapTuple)->t_data
								  + offsetof(HeapTupleHeaderData, t_bits));
		fc_targetData = (char *) (*fc_targetHeapTuple)->t_data + fc_hoff;
		fc_infoMask = &(fc_targetTHeader->t_infomask);
	}
	else
	{
		fc_len += SizeofMinimalTupleHeader;
		fc_hoff = fc_len = MAXALIGN(fc_len); /* 安全对齐用户数据 */
		fc_len += fc_targetDataLen;

		*fc_targetMinimalTuple = (MinimalTuple) palloc0(fc_len);
		(*fc_targetMinimalTuple)->t_len = fc_len;
		(*fc_targetMinimalTuple)->t_hoff = fc_hoff + MINIMAL_TUPLE_OFFSET;
		(*fc_targetMinimalTuple)->t_infomask = fc_sourceTHeader->t_infomask;
		/* 相同的宏适用于MinimalTuples */
		HeapTupleHeaderSetNatts(*fc_targetMinimalTuple, fc_natts);
		if (fc_targetNullLen > 0)
			fc_nullBits = (bits8 *) ((char *) *fc_targetMinimalTuple
								  + offsetof(MinimalTupleData, t_bits));
		fc_targetData = (char *) *fc_targetMinimalTuple + fc_hoff;
		fc_infoMask = &((*fc_targetMinimalTuple)->t_infomask);
	}

	if (fc_targetNullLen > 0)
	{
		if (fc_sourceNullLen > 0)
		{
			/* 如果位图已存在，则复制 - 一切均已设置 */
			memcpy(fc_nullBits,
				   ((char *) fc_sourceTHeader)
				   + offsetof(HeapTupleHeaderData, t_bits),
				   fc_sourceNullLen);
			fc_nullBits += fc_sourceNullLen - 1;
		}
		else
		{
			fc_sourceNullLen = BITMAPLEN(fc_sourceNatts);
			/* 为所有现有属性设置NOT NULL */
			memset(fc_nullBits, 0xff, fc_sourceNullLen);

			fc_nullBits += fc_sourceNullLen - 1;

			if (fc_sourceNatts & 0x07)
			{
				/* 构建掩码（反转的！） */
				fc_bitMask = 0xff << (fc_sourceNatts & 0x07);
				/* 啊哈 */
				*fc_nullBits = ~fc_bitMask;
			}
		}

		fc_bitMask = (1 << ((fc_sourceNatts - 1) & 0x07));
	}							/* 如果有null位图，则结束 */

	memcpy(fc_targetData,
		   ((char *) fc_sourceTuple->t_data) + fc_sourceTHeader->t_hoff,
		   fc_sourceDataLen);

	fc_targetData += fc_sourceDataLen;

	/* 现在填充缺失值 */
	for (fc_attnum = fc_sourceNatts; fc_attnum < fc_natts; fc_attnum++)
	{

		Form_pg_attribute fc_attr = TupleDescAttr(fc_tupleDesc, fc_attnum);

		if (fc_attrmiss && fc_attrmiss[fc_attnum].am_present)
		{
			fc_fill_val(fc_attr,
					 fc_nullBits ? &fc_nullBits : NULL,
					 &fc_bitMask,
					 &fc_targetData,
					 fc_infoMask,
					 fc_attrmiss[fc_attnum].am_value,
					 false);
		}
		else
		{
			fc_fill_val(fc_attr,
					 &fc_nullBits,
					 &fc_bitMask,
					 &fc_targetData,
					 fc_infoMask,
					 (Datum) 0,
					 true);
		}
	}							/* 结束对缺失属性的循环 */
}

/*
 * 为最小HeapTuple填充缺失值
 */
MinimalTuple minimal_expand_tuple(HeapTuple fc_sourceTuple, TupleDesc fc_tupleDesc)
{
	MinimalTuple fc_minimalTuple;

	fc_expand_tuple(NULL, &fc_minimalTuple, fc_sourceTuple, fc_tupleDesc);
	return fc_minimalTuple;
}

/*
 * 为普通HeapTuple填充缺失值
 */
HeapTuple heap_expand_tuple(HeapTuple fc_sourceTuple, TupleDesc fc_tupleDesc)
{
	HeapTuple	fc_heapTuple;

	fc_expand_tuple(&fc_heapTuple, NULL, fc_sourceTuple, fc_tupleDesc);
	return fc_heapTuple;
}

/* ----------------
 *		heap_copy_tuple_as_datum
 *
 *		将元组复制为复合类型Datum
 * ----------------
 */
Datum heap_copy_tuple_as_datum(HeapTuple fc_tuple, TupleDesc fc_tupleDesc)
{
	HeapTupleHeader fc_td;

	/*
	 * 如果元组包含任何外部TOAST指针，我们必须将这些字段内联
	 * 以符合复合类型Datum的约定。
	 */
	if (HeapTupleHasExternal(fc_tuple))
		return toast_flatten_tuple_to_datum(fc_tuple->t_data,
											fc_tuple->t_len,
											fc_tupleDesc);

	/*
	 * 处理简单情况的快速路径：只需进行palloc的复制并插入
	 * 正确的复合-Datum头字段（因为如果给定的元组来自磁盘，而不是来自heap_form_tuple，
	 * 则这些可能未设置）。
	 */
	fc_td = (HeapTupleHeader) palloc(fc_tuple->t_len);
	memcpy((char *) fc_td, (char *) fc_tuple->t_data, fc_tuple->t_len);

	HeapTupleHeaderSetDatumLength(fc_td, fc_tuple->t_len);
	HeapTupleHeaderSetTypeId(fc_td, fc_tupleDesc->tdtypeid);
	HeapTupleHeaderSetTypMod(fc_td, fc_tupleDesc->tdtypmod);

	return PointerGetDatum(fc_td);
}

/*
 * heap_form_tuple
 *		从给定的values[]和isnull[]数组构造一个元组，
 *		其长度由tupleDescriptor->natts指示。
 *
 * 结果分配在当前内存上下文中。
 */
HeapTuple heap_form_tuple(TupleDesc fc_tupleDescriptor,
				Datum *fc_values,
				bool *fc_isnull)
{
	HeapTuple	fc_tuple;			/* 返回元组 */
	HeapTupleHeader fc_td;			/* 元组数据 */
	Size		fc_len,
				fc_data_len;
	int			fc_hoff;
	bool		fc_hasnull = false;
	int			fc_numberOfAttributes = fc_tupleDescriptor->natts;
	int			fc_i;

	if (fc_numberOfAttributes > MaxTupleAttributeNumber)
		ereport(ERROR,
				(errcode(ERRCODE_TOO_MANY_COLUMNS),
				 errmsg("number of columns (%d) exceeds limit (%d)",
						fc_numberOfAttributes, MaxTupleAttributeNumber)));

	/*
	 * 检查空值
	 */
	for (fc_i = 0; fc_i < fc_numberOfAttributes; fc_i++)
	{
		if (fc_isnull[fc_i])
		{
			fc_hasnull = true;
			break;
		}
	}

	/*
	 * 确定所需的总空间
	 */
	fc_len = offsetof(HeapTupleHeaderData, t_bits);

	if (fc_hasnull)
		fc_len += BITMAPLEN(fc_numberOfAttributes);

	fc_hoff = fc_len = MAXALIGN(fc_len); /* 安全对齐用户数据 */

	fc_data_len = heap_compute_data_size(fc_tupleDescriptor, fc_values, fc_isnull);

	fc_len += fc_data_len;

	/*
	 * 分配并清零所需的空间。注意，元组主体和
	 * HeapTupleData管理结构是以一块分配的。
	 */
	fc_tuple = (HeapTuple) palloc0(HEAPTUPLESIZE + fc_len);
	fc_tuple->t_data = fc_td = (HeapTupleHeader) ((char *) fc_tuple + HEAPTUPLESIZE);

	/*
	 * 并填充信息。注意，我们填充Datum字段尽管
	 * 此元组可能永远不会成为Datum。这使得HeapTupleHeaderGetDatum
	 * 在需要时识别元组类型。
	 */
	fc_tuple->t_len = fc_len;
	ItemPointerSetInvalid(&(fc_tuple->t_self));
	fc_tuple->t_tableOid = InvalidOid;

	HeapTupleHeaderSetDatumLength(fc_td, fc_len);
	HeapTupleHeaderSetTypeId(fc_td, fc_tupleDescriptor->tdtypeid);
	HeapTupleHeaderSetTypMod(fc_td, fc_tupleDescriptor->tdtypmod);
	/* 我们还确保t_ctid无效，除非明确设置 */
	ItemPointerSetInvalid(&(fc_td->t_ctid));

	HeapTupleHeaderSetNatts(fc_td, fc_numberOfAttributes);
	fc_td->t_hoff = fc_hoff;

	heap_fill_tuple(fc_tupleDescriptor,
					fc_values,
					fc_isnull,
					(char *) fc_td + fc_hoff,
					fc_data_len,
					&fc_td->t_infomask,
					(fc_hasnull ? fc_td->t_bits : NULL));

	return fc_tuple;
}

/*
 * heap_modify_tuple
 *		根据旧元组和一组替换值形成一个新元组。
 *
 * replValues、replIsnull和doReplace数组的长度
 * 必须由tupleDesc->natts指示。新元组使用
 * replValues/replIsnull中的数据构造，如果doReplace为true，
 * 则使用旧元组中的数据，如果doReplace为false。
 *
 * 结果分配在当前内存上下文中。
 */
HeapTuple heap_modify_tuple(HeapTuple fc_tuple,
				  TupleDesc fc_tupleDesc,
				  Datum *fc_replValues,
				  bool *fc_replIsnull,
				  bool *fc_doReplace)
{
	int			fc_numberOfAttributes = fc_tupleDesc->natts;
	int			fc_attoff;
	Datum	   *fc_values;
	bool	   *fc_isnull;
	HeapTuple	fc_newTuple;

	/*
	 * 从元组或替换信息中分配并填充值和 isnull 数组，根据需要。
	 *
	 * 注意：这里使用 heap_deform_tuple() 还是仅使用 heap_getattr() 仅替换未被替换的列是有争议的。 
	 * 如果被替换的列很多而未被替换的列很少，后者可能更优。然而，heap_deform_tuple 的成本仅为 O(N)，而 
	 * heap_getattr 的方法在存在许多未被替换的列时成本为 O(N^2)，因此似乎更好在线性成本的范围内出错。
	 */
	fc_values = (Datum *) palloc(fc_numberOfAttributes * sizeof(Datum));
	fc_isnull = (bool *) palloc(fc_numberOfAttributes * sizeof(bool));

	heap_deform_tuple(fc_tuple, fc_tupleDesc, fc_values, fc_isnull);

	for (fc_attoff = 0; fc_attoff < fc_numberOfAttributes; fc_attoff++)
	{
		if (fc_doReplace[fc_attoff])
		{
			fc_values[fc_attoff] = fc_replValues[fc_attoff];
			fc_isnull[fc_attoff] = fc_replIsnull[fc_attoff];
		}
	}

	/*
	 * 从值和 isnull 数组创建一个新的元组
	 */
	fc_newTuple = heap_form_tuple(fc_tupleDesc, fc_values, fc_isnull);

	pfree(fc_values);
	pfree(fc_isnull);

	/*
	 * 复制旧元组的识别信息：t_ctid, t_self
	 */
	fc_newTuple->t_data->t_ctid = fc_tuple->t_data->t_ctid;
	fc_newTuple->t_self = fc_tuple->t_self;
	fc_newTuple->t_tableOid = fc_tuple->t_tableOid;

	return fc_newTuple;
}

/*
 * heap_modify_tuple_by_cols
 *		从旧元组和一组替换值形成一个新元组。
 *
 * 这类似于 heap_modify_tuple，除了不是通过布尔映射指定要替换的列，而是使用目标列号数组。
 * 当要替换的列数是固定的时，这通常更方便。 replCols，replValues 和 replIsnull 数组的长度必须
 * 为 nCols。目标列号从 1 开始索引。
 *
 * 结果在当前内存上下文中分配。
 */
HeapTuple heap_modify_tuple_by_cols(HeapTuple fc_tuple,
						  TupleDesc fc_tupleDesc,
						  int fc_nCols,
						  int *fc_replCols,
						  Datum *fc_replValues,
						  bool *fc_replIsnull)
{
	int			fc_numberOfAttributes = fc_tupleDesc->natts;
	Datum	   *fc_values;
	bool	   *fc_isnull;
	HeapTuple	fc_newTuple;
	int			fc_i;

	/*
	 * 从元组中分配并填充值和 isnull 数组，然后从输入数组替换所选列。
	 */
	fc_values = (Datum *) palloc(fc_numberOfAttributes * sizeof(Datum));
	fc_isnull = (bool *) palloc(fc_numberOfAttributes * sizeof(bool));

	heap_deform_tuple(fc_tuple, fc_tupleDesc, fc_values, fc_isnull);

	for (fc_i = 0; fc_i < fc_nCols; fc_i++)
	{
		int			fc_attnum = fc_replCols[fc_i];

		if (fc_attnum <= 0 || fc_attnum > fc_numberOfAttributes)
			elog(ERROR, "invalid column number %d", fc_attnum);
		fc_values[fc_attnum - 1] = fc_replValues[fc_i];
		fc_isnull[fc_attnum - 1] = fc_replIsnull[fc_i];
	}

	/*
	 * 从值和 isnull 数组创建一个新的元组
	 */
	fc_newTuple = heap_form_tuple(fc_tupleDesc, fc_values, fc_isnull);

	pfree(fc_values);
	pfree(fc_isnull);

	/*
	 * 复制旧元组的识别信息：t_ctid, t_self
	 */
	fc_newTuple->t_data->t_ctid = fc_tuple->t_data->t_ctid;
	fc_newTuple->t_self = fc_tuple->t_self;
	fc_newTuple->t_tableOid = fc_tuple->t_tableOid;

	return fc_newTuple;
}

/*
 * heap_deform_tuple
 *		给定一个元组，将数据提取到值/isnull 数组中；这是 heap_form_tuple 的逆操作。
 *
 *		值/isnull 数组的存储由调用者提供；
 *		它应该根据 tupleDesc->natts 的大小来调整，而不是
 *		HeapTupleHeaderGetNatts(tuple->t_data)。
 *
 *		注意，对于通过引用传递的数据类型，放置在 Datum 中的指针将指向给定元组。
 *
 *		当需要提取元组的所有或大多数字段时，
 *		该例程将比围绕 heap_getattr 的循环显著更快；一旦涉及任何
 *		不可缓存的属性偏移量，循环的成本将变为 O(N^2)。
 */
void heap_deform_tuple(HeapTuple fc_tuple, TupleDesc fc_tupleDesc,
				  Datum *fc_values, bool *fc_isnull)
{
	HeapTupleHeader fc_tup = fc_tuple->t_data;
	bool		fc_hasnulls = HeapTupleHasNulls(fc_tuple);
	int			fc_tdesc_natts = fc_tupleDesc->natts;
	int			fc_natts;			/* 要提取的属性数量 */
	int			fc_attnum;
	char	   *fc_tp;				/* 指向元组数据的指针 */
	uint32		fc_off;			/* 在元组数据中的偏移量 */
	bits8	   *fc_bp = fc_tup->t_bits;	/* 指向元组中 null 位图的指针 */
	bool		fc_slow = false;	/* 我们可以使用/设置 attcacheoff 吗？ */

	fc_natts = HeapTupleHeaderGetNatts(fc_tup);

	/*
	 * 在继承情况下，给定的元组可能实际包含比调用者预期的更多字段。
	 * 不要越过调用者数组的边界。
	 */
	fc_natts = Min(fc_natts, fc_tdesc_natts);

	fc_tp = (char *) fc_tup + fc_tup->t_hoff;

	fc_off = 0;

	for (fc_attnum = 0; fc_attnum < fc_natts; fc_attnum++)
	{
		Form_pg_attribute fc_thisatt = TupleDescAttr(fc_tupleDesc, fc_attnum);

		if (fc_hasnulls && att_isnull(fc_attnum, fc_bp))
		{
			fc_values[fc_attnum] = (Datum) 0;
			fc_isnull[fc_attnum] = true;
			fc_slow = true;		/* 不能再使用 attcacheoff */
			continue;
		}

		fc_isnull[fc_attnum] = false;

		if (!fc_slow && fc_thisatt->attcacheoff >= 0)
			fc_off = fc_thisatt->attcacheoff;
		else if (fc_thisatt->attlen == -1)
		{
			/*
			 * 我们只能缓存 varlena 属性的偏移量，如果偏移量已经适当对齐，
			 * 以确保在任何情况下都没有填充字节：这样偏移量将适用于对齐或未对齐的值。
			 */
			if (!fc_slow &&
				fc_off == att_align_nominal(fc_off, fc_thisatt->attalign))
				fc_thisatt->attcacheoff = fc_off;
			else
			{
				fc_off = att_align_pointer(fc_off, fc_thisatt->attalign, -1,
										fc_tp + fc_off);
				fc_slow = true;
			}
		}
		else
		{
			/* 不是 varlena，因此可以安全使用 att_align_nominal */
			fc_off = att_align_nominal(fc_off, fc_thisatt->attalign);

			if (!fc_slow)
				fc_thisatt->attcacheoff = fc_off;
		}

		fc_values[fc_attnum] = fetchatt(fc_thisatt, fc_tp + fc_off);

		fc_off = att_addlength_pointer(fc_off, fc_thisatt->attlen, fc_tp + fc_off);

		if (fc_thisatt->attlen <= 0)
			fc_slow = true;		/* 不能再使用 attcacheoff */
	}

	/*
	 * 如果元组没有所有由 tupleDesc 指定的属性，请将其余部分
	 * 读取为 null 或适当的缺失值。
	 */
	for (; fc_attnum < fc_tdesc_natts; fc_attnum++)
		fc_values[fc_attnum] = getmissingattr(fc_tupleDesc, fc_attnum + 1, &fc_isnull[fc_attnum]);
}

/*
 * heap_freetuple
 */
void heap_freetuple(HeapTuple fc_htup)
{
	pfree(fc_htup);
}


/*
 * heap_form_minimal_tuple
 *		从给定的 values[] 和 isnull[] 数组构造一个 MinimalTuple，
 *		这些数组的长度由 tupleDescriptor->natts 指定。
 *
 * 这与 heap_form_tuple() 一样，只不过结果是一个
 * "最小"元组，缺少 HeapTupleData 头部以及系统
 * 列的空间。
 *
 * 结果分配在当前内存上下文中。
 */
MinimalTuple heap_form_minimal_tuple(TupleDesc fc_tupleDescriptor,
						Datum *fc_values,
						bool *fc_isnull)
{
	MinimalTuple fc_tuple;			/* 返回元组 */
	Size		fc_len,
				fc_data_len;
	int			fc_hoff;
	bool		fc_hasnull = false;
	int			fc_numberOfAttributes = fc_tupleDescriptor->natts;
	int			fc_i;

	if (fc_numberOfAttributes > MaxTupleAttributeNumber)
		ereport(ERROR,
				(errcode(ERRCODE_TOO_MANY_COLUMNS),
				 errmsg("number of columns (%d) exceeds limit (%d)",
						fc_numberOfAttributes, MaxTupleAttributeNumber)));

	/*
	 * 检查空值
	 */
	for (fc_i = 0; fc_i < fc_numberOfAttributes; fc_i++)
	{
		if (fc_isnull[fc_i])
		{
			fc_hasnull = true;
			break;
		}
	}

	/*
	 * 确定所需的总空间
	 */
	fc_len = SizeofMinimalTupleHeader;

	if (fc_hasnull)
		fc_len += BITMAPLEN(fc_numberOfAttributes);

	fc_hoff = fc_len = MAXALIGN(fc_len); /* 安全对齐用户数据 */

	fc_data_len = heap_compute_data_size(fc_tupleDescriptor, fc_values, fc_isnull);

	fc_len += fc_data_len;

	/*
	 * 分配并清零所需的空间。
	 */
	fc_tuple = (MinimalTuple) palloc0(fc_len);

	/*
	 * 并填充信息。
	 */
	fc_tuple->t_len = fc_len;
	HeapTupleHeaderSetNatts(fc_tuple, fc_numberOfAttributes);
	fc_tuple->t_hoff = fc_hoff + MINIMAL_TUPLE_OFFSET;

	heap_fill_tuple(fc_tupleDescriptor,
					fc_values,
					fc_isnull,
					(char *) fc_tuple + fc_hoff,
					fc_data_len,
					&fc_tuple->t_infomask,
					(fc_hasnull ? fc_tuple->t_bits : NULL));

	return fc_tuple;
}

/*
 * heap_free_minimal_tuple
 */
void heap_free_minimal_tuple(MinimalTuple fc_mtup)
{
	pfree(fc_mtup);
}

/*
 * heap_copy_minimal_tuple
 *		复制一个 MinimalTuple
 *
 * 结果分配在当前内存上下文中。
 */
MinimalTuple heap_copy_minimal_tuple(MinimalTuple fc_mtup)
{
	MinimalTuple fc_result;

	fc_result = (MinimalTuple) palloc(fc_mtup->t_len);
	memcpy(fc_result, fc_mtup, fc_mtup->t_len);
	return fc_result;
}

/*
 * heap_tuple_from_minimal_tuple
 *		通过从 MinimalTuple 复制来创建 HeapTuple；
 *		系统列用零填充
 *
 * 结果分配在当前内存上下文中。
 * HeapTuple 结构、元组头和元组数据都作为一个单一的 palloc() 块分配。
 */
HeapTuple heap_tuple_from_minimal_tuple(MinimalTuple fc_mtup)
{
	HeapTuple	fc_result;
	uint32		fc_len = fc_mtup->t_len + MINIMAL_TUPLE_OFFSET;

	fc_result = (HeapTuple) palloc(HEAPTUPLESIZE + fc_len);
	fc_result->t_len = fc_len;
	ItemPointerSetInvalid(&(fc_result->t_self));
	fc_result->t_tableOid = InvalidOid;
	fc_result->t_data = (HeapTupleHeader) ((char *) fc_result + HEAPTUPLESIZE);
	memcpy((char *) fc_result->t_data + MINIMAL_TUPLE_OFFSET, fc_mtup, fc_mtup->t_len);
	memset(fc_result->t_data, 0, offsetof(HeapTupleHeaderData, t_infomask2));
	return fc_result;
}

/*
 * minimal_tuple_from_heap_tuple
 *		通过从 HeapTuple 复制来创建 MinimalTuple
 *
 * 结果分配在当前内存上下文中。
 */
MinimalTuple minimal_tuple_from_heap_tuple(HeapTuple fc_htup)
{
	MinimalTuple fc_result;
	uint32		fc_len;

	Assert(fc_htup->t_len > MINIMAL_TUPLE_OFFSET);
	fc_len = fc_htup->t_len - MINIMAL_TUPLE_OFFSET;
	fc_result = (MinimalTuple) palloc(fc_len);
	memcpy(fc_result, (char *) fc_htup->t_data + MINIMAL_TUPLE_OFFSET, fc_len);
	fc_result->t_len = fc_len;
	return fc_result;
}

/*
 * 这主要存在是为了让 JIT 可以内联定义，但在调试会话中有时
 * 也很有用。
 */
size_t varsize_any(void *fc_p)
{
	return VARSIZE_ANY(fc_p);
}
